#include "API.h"
#include "windows.h"
#include <iostream>
#include <mpi.h>

extern void calculateAccurateSolution(Matrix &result);
extern void calculateNumericalResultExplicit(Matrix &result);
extern void calculateNumericalResultImplicit(Matrix &result);

int main(int argc, char **argv) {
	MPI_Init(NULL, NULL);
	int procNum, rank;
	MPI_Comm_size(MPI_COMM_WORLD, &procNum);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	//std::cout << "num of proc = " << procNum << '\n';
	if (procNum != 2)
	{
		std::cout << "PSHEL, OK";
		return 0;
	}
	if (rank == 0){
		Timer timer;
		timer.start();
		Matrix accurate(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		calculateAccurateSolution(accurate);
		//accurate.output               ();
		std::cout << std::endl;



		timer.restart();
		Matrix numerical_explicit(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		calculateNumericalResultExplicit(numerical_explicit);
		//  numerical_explicit.output   ();
		std::cout << std::endl;


		timer.restart();
		Matrix error_explicit(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		error_explicit.calculateError(numerical_explicit, accurate);
		//  error_explicit.output       ();
		timer.restart();

		std::cout << std::endl;
		std::cout << "Avr error explicit: " << error_explicit.average();
		std::cout << std::endl;

		Matrix absolute_error_explicit(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		absolute_error_explicit.calculateAbsoluteError(numerical_explicit, accurate);
		//  absolute_error_explicit.output ();
		timer.restart();
		std::cout << std::endl;
		std::cout << "Avr error explicit(absolute): " << absolute_error_explicit.average();
		std::cout << std::endl;

		/////////////////
		////////////////
		/////////////////
		///////////////



		Matrix numerical_implicit(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		calculateNumericalResultImplicit(numerical_implicit);
		//numerical_implicit.output();
		std::cout << std::endl;
		timer.stop();


		/////////////////
		////////////////
		/////////////////
		///////////////

		//for (int i = 0; i < timer.size(); ++i)
		//  std::cout << timer.get(i) << std::endl;
		Matrix error_implicit(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		error_implicit.calculateError(numerical_implicit, accurate);
		//  error_implicit.output       ();

		//numerical_implicit.serialize("plot3d2.dat");
		//accurate.serialize("accurate3d2.dat");

		std::cout << std::endl;
		std::cout << "Avr error implicit: " << error_implicit.average();
		std::cout << std::endl;

		Matrix absolute_error_implicit(Constant::T_GRID_SIZE, Constant::X_GRID_SIZE);
		absolute_error_implicit.calculateAbsoluteError(numerical_implicit, accurate);
		//  absolute_error_implicit.output ();


		std::cout << std::endl;
		std::cout << "Avr error implicit(absolute): " << absolute_error_implicit.average();

		std::cout << std::endl;
		int check = 0;
	}

	std::cout << "rank =" << rank << "\t";
	if (rank != 0)
	{
		const int size = (int)((double)(Constant::NUM_OF_EQUATIONS) / 2);
		double *temp = new double[size];
		Matrix matrix(Constant::NUM_OF_EQUATIONS, Constant::NUM_OF_EQUATIONS + 1);
		Vector vector(size);
		MPI_Status status;
		double data[Constant::TRIDIAGONAL_MATRIX_SIZE];
		int stop = 0;
		while (true)
		{
			MPI_Recv(&stop, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
			if (stop)
			{
				break;
			}
			MPI_Recv(data, Constant::TRIDIAGONAL_MATRIX_SIZE, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &status);

			for (int i = 0; i < Constant::NUM_OF_EQUATIONS; i++)
			{
				for (int j = 0; j <= Constant::NUM_OF_EQUATIONS; j++)
				{
					matrix.set(i, j, data[i * (Constant::NUM_OF_EQUATIONS + 1) + j]);
				}
			}
			Matrix::thomasLeft(matrix, vector);
			for (int i = 0; i < size; i++)
			{
				temp[i] = vector.data()[i];
			}
			MPI_Ssend(temp, size, MPI_DOUBLE, 0, 1, MPI_COMM_WORLD);
		}
	}
	MPI_Finalize();

	return                      0;
}